//! ## Client C APIs for Command Queue in CS2 System
//!
//! This module provides C APIs to help clients in CS2 system.

use std::{ffi, slice};

use cs2s::sign::CodeSign;

/// Calculate the signature of the given guest code.
///
/// ## Safety
///
/// The provided pointers must be valid.
/// - `guest_code` must point to a valid memory region with the given size.
/// - `guest_sign` must point to a valid memory region.
///
/// ## Returns
///
/// - `0` on success.
/// - `-EINVAL` if any of the provided pointers are null or the given size is zero.
#[export_name = "cs2s_helper_calc_sign"]
pub unsafe extern "C" fn calc_sign(
    guest_code: *const ffi::c_void,
    guest_size: usize,
    guest_sign: *mut CodeSign,
) -> ffi::c_int {
    if guest_code.is_null() || guest_size == 0 || guest_sign.is_null() {
        return -libc::EINVAL;
    }

    // Safety: The provided `guest_code` and `guest_size` are guaranteed to be valid by
    // the caller.
    unsafe {
        guest_sign.write(slice::from_raw_parts(guest_code as *const u8, guest_size).into());
    }

    0
}

/// Test if the given code signs are equal.
///
/// ## Safety
///
/// The provided pointers must be valid.
/// - `sign1` must point to a valid code sign.
/// - `sign2` must point to a valid code sign.
///
/// ## Returns
///
/// - `true` if the given code signs are both not null and equal.
/// - `false` otherwise.
#[export_name = "cs2s_helper_test_sign"]
pub unsafe extern "C" fn test_sign(sign1: *const CodeSign, sign2: *const CodeSign) -> bool {
    if sign1.is_null() || sign2.is_null() {
        return false;
    }

    // Safety: The provided `sign1` and `sign2` are guaranteed to be valid by the caller.
    let (sign1, sign2) = unsafe { (&*sign1, &*sign2) };

    sign1 == sign2
}

#[cfg(test)]
mod test {
    use std::{mem, ptr};

    use cs2s_testdata::*;

    use super::*;

    #[test]
    fn calculate_code_sign() {
        assert_eq!(
            // Safety: The provided pointers are valid.
            unsafe {
                let mut guest_sign = mem::zeroed();
                calc_sign(
                    GUEST_CODE_0.as_ptr() as _,
                    GUEST_CODE_0.len(),
                    &mut guest_sign,
                )
            },
            0
        );
    }

    #[test]
    fn test_code_sign_eq() {
        // Safety: The provided pointers are valid.
        let guest_sign_0 = unsafe {
            let mut sign = mem::zeroed();
            calc_sign(GUEST_CODE_0.as_ptr() as _, GUEST_CODE_0.len(), &mut sign);
            sign
        };

        // Safety: One of the provided pointers is null, so we expect false.
        assert!(!unsafe { test_sign(&guest_sign_0, ptr::null()) });

        // Safety: The provided pointers are valid.
        assert!(unsafe { test_sign(&guest_sign_0, &guest_sign_0) });

        // Safety: The provided pointers are valid.
        let guest_sign_1 = unsafe {
            let mut sign = mem::zeroed();
            calc_sign(GUEST_CODE_1.as_ptr() as _, GUEST_CODE_1.len(), &mut sign);
            sign
        };

        // Safety: The provided pointers are valid.
        assert!(!unsafe { test_sign(&guest_sign_0, &guest_sign_1) });

        // Safety: The provided pointers are null, so we expect false.
        assert!(!unsafe { test_sign(ptr::null(), ptr::null()) });
    }

    #[test]
    fn invalid_args() {
        assert_eq!(
            // Safety: The provided pointers are null, so we expect an error return.
            unsafe { calc_sign(ptr::null(), 0, ptr::null_mut()) },
            -libc::EINVAL
        );
    }
}
